package com.softserveinc.ita.kaiji.multiplay.dao;

import com.softserveinc.ita.kaiji.multiplay.KaijiException;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Project testFromEmpty
 * Created by mikeldpl
 * 21.03.2015 23:45.
 */
public class InMemoryDAO<T, ID extends Serializable> implements DAO<T, ID> {

	private ConcurrentHashMap<ID, T> store = new ConcurrentHashMap<>();
	private Method idM;
	private Field idF;

	public InMemoryDAO(Class<T> tClass) {
		Class entClass = tClass;
		while (entClass != null && !entClass.isAnnotationPresent(Entity.class))
			entClass = entClass.getSuperclass();
		if (entClass == null)
			throw new IllegalArgumentException(tClass + " haven't @Entity annotation");
		setIdM(entClass);
	}

	private void setIdM(Class<T> tClass) {
		for (Field f : tClass.getDeclaredFields()) {
			if (f.isAnnotationPresent(Id.class)) {
				idF = f;
				break;
			}
		}
		if (idF == null)
			throw new IllegalArgumentException("Problem with Id in type: " + tClass);
		try {
			idM = tClass.getMethod("get" + Character.toUpperCase(idF.getName().charAt(0)) + idF.getName().substring(1));
		} catch (NoSuchMethodException e) {
			if (!idF.isAccessible())
				idF.setAccessible(true);
		}
		if (idM != null && !idM.isAccessible()) {
			idM.setAccessible(true);
		}
	}

	private ID getIdFromDomain(T domain) {
		try {
			return (ID) (idM != null ? idM.invoke(domain) : idF.get(domain));
		} catch (IllegalAccessException | InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}

	public void save(T domain) {
		if (store.contains(domain))
			throw new KaijiException("Can't save " + domain);
		store.put(getIdFromDomain(domain), domain);
	}

	public void delete(ID id) {
		store.remove(id);
	}

	public T findOne(ID id) {
		return store.get(id);
	}

	public boolean contains(ID id) {
		return store.contains(id);
	}

	@Override
	public Collection<T> getAll() {
		return store.values();
	}
}
